---
type: tutorial
title: 生成标签页面
description: |-
  教程：搭建你的 Astro 博客 -
  使用 getStaticPaths() 一次性创建多个页面（路由）
---
import Box from '~/components/tutorial/Box.astro';
import Checklist from '~/components/Checklist.astro';
import MultipleChoice from '~/components/tutorial/MultipleChoice.astro';
import Option from '~/components/tutorial/Option.astro';
import PreCheck from '~/components/tutorial/PreCheck.astro';
import { Steps } from '@astrojs/starlight/components';

<PreCheck>
  - 创建一个页面以生成多个页面
  - 指定要构建的页面路由，并为每个页面传递各自的属性
</PreCheck>

## 动态页面路由

你可以使用 `.astro` 文件创建整套动态页面，这些文件需要向外暴露一个 `getStaticPaths()` 函数。

## 动态创建页面

<Steps>
1. 创建一个新文件：`src/pages/tags/[tag].astro`。（你需要创建一个新文件夹。）注意文件名（`[tag].astro`）使用方括号。将以下代码粘贴到文件中：

    ```astro title="src/pages/tags/[tag].astro"
    ---
    import BaseLayout from '../../layouts/BaseLayout.astro';

    export async function getStaticPaths() {
      return [
        { params: { tag: "astro" } },
        { params: { tag: "successes" } },
        { params: { tag: "community" } },
        { params: { tag: "blogging" } },
        { params: { tag: "setbacks" } },
        { params: { tag: "learning in public" } },
      ];
    }

    const { tag } = Astro.params;
    ---
    <BaseLayout pageTitle={tag}>
      <p>包含「{tag}」标签的文章</p>
    </BaseLayout>
    ```

    `getStaticPaths` 函数返回一个页面路由数组，这些页面将使用文件中定义的相同模板。

2. 如果你已经自定义了博客文章，用你自己的文章中使用的标签替换各个标签值（例如 "astro"、"successes"、"community" 等）。

3. 确保每篇博客文章至少包含一个标签，以数组的形式编写，例如 `tags: ["blogging"]`。

4. 在浏览器中访问 `http://localhost:4321/tags/astro`，你应该能看到一个页面，它是从 `[tag].astro` 动态生成的。检查是否还为每个标签创建了页面，例如 `/tags/successes`、`/tags/community`、`/tags/learning%20in%20public` 等，或者你自定义的标签。你可能需要先退出并重新启动开发服务器才能看到这些新页面。
</Steps>

## 在动态路由中使用 props

<Steps>
1. 在你的 `getStaticPaths()` 函数中添加以下 props，以便使所有博客文章的数据对每个页面路由可用。

    确保为数组中的每个路由都添加新的 props，并将这些 props 在函数之外的组件模板中可用。

    ```astro title="src/pages/tags/[tag].astro" ins={5,18} ins="props: {posts: allPosts}" 
    ---
    import BaseLayout from '../../layouts/BaseLayout.astro';

    export async function getStaticPaths() {
      const allPosts = Object.values(import.meta.glob('../posts/*.md', { eager: true }));

      return [
        {params: {tag: "astro"}, props: {posts: allPosts}},
        {params: {tag: "successes"}, props: {posts: allPosts}},
        {params: {tag: "community"}, props: {posts: allPosts}},
        {params: {tag: "blogging"}, props: {posts: allPosts}},
        {params: {tag: "setbacks"}, props: {posts: allPosts}},
        {params: {tag: "learning in public"}, props: {posts: allPosts}}
      ];
    }
    
    const { tag } = Astro.params;
    const { posts } = Astro.props;
    ---
    ```

2. 过滤你的文章列表，使用 Astro 内置的 TypeScript 支持，筛选出仅包含页面所属标签的文章。

    ```astro title="src/pages/tags/[tag].astro" ins={4}
    ---
    const { tag } = Astro.params;
    const { posts } = Astro.props;
    const filteredPosts = posts.filter((post: any) => post.frontmatter.tags?.includes(tag));
    ---
    ```

3. 现在，你可以更新 HTML 模板以显示包含页面所属标签的每篇博客文章列表。将以下代码添加到 `[tag].astro`：

    ```astro title="src/pages/tags/[tag].astro" ins={3-5}
    <BaseLayout pageTitle={tag}>   
      <p>包含「{tag}」标签的文章</p>
      <ul>
        {filteredPosts.map((post: any) => <li><a href={post.url}>{post.frontmatter.title}</a></li>)}
      </ul>
    </BaseLayout>
    ```

4. 你甚至可以将此代码重构为使用 `<BlogPost />` 组件！（不要忘记在 `[tag].astro` 的顶部导入该组件。）

    ```astro title="src/pages/tags/[tag].astro" del={4} ins={5}
    <BaseLayout pageTitle={tag}>
      <p>包含「{tag}」标签的文章</p>
      <ul>
        {filteredPosts.map((post: any) => <li><a href={post.url}>{post.frontmatter.title}</a></li>)}
        {filteredPosts.map((post: any) => <BlogPost url={post.url} title={post.frontmatter.title}/>)}
      </ul>
    </BaseLayout>
    ```

5. 在浏览器中的预览中查看各个标签页面，现在你应该能看到包含特定标签的所有博客文章列表。
</Steps>

<Box icon="question-mark">

### 分析代码

对于以下每个问题，请指出代码是在 `getStaticPath()` 函数的**内部**还是在其**外部**编写的。

1. `import.meta.glob()` 调用以接收有关所有 `.md` 文件的信息，并将其传递给每个页面路由。

    <MultipleChoice>
    <Option isCorrect>在 `getStaticPaths` 内部</Option>
    <Option>在 `getStaticPaths` 外部</Option>
    </MultipleChoice>

2. `getStaticPaths()` 要生成（返回的）路由列表的代码。

    <MultipleChoice>
    <Option isCorrect>在 `getStaticPaths` 内部</Option>
    <Option>在 `getStaticPaths` 外部</Option>
    </MultipleChoice>

3. 用于在 HTML 模板中使用的 `props` 和 `params` 的值。

    <MultipleChoice>
    <Option>在 `getStaticPaths` 内部</Option>
    <Option isCorrect>在 `getStaticPaths` 外部</Option>
    </MultipleChoice>
</Box>

:::note[要点]
如果需要构建页面路由的信息，请将其编写在 `getStaticPaths()` **内部**。

要在页面路由的 HTML 模板中接收信息，请将其编写在 `getStaticPaths()` **外部**。
:::

## 进阶 JavaScript：从现有标签生成页面

现在，你的标签页面在 `[tag].astro` 中静态定义。如果你在博客文章中添加了新标签，你还必须重新访问该页面并更新你的页面路由。

以下示例显示了如何用新的代码替换此页面上的代码，该代码将自动查找并为博客页面上使用的每个标签生成页面。

:::note
即使看起来有挑战性，你也可以尝试按照步骤自己构建此功能！如果你现在不想跟着编写所需的 JavaScript 代码，可以直接跳到[代码的最终版本](#最终代码示例)，并将其直接用于你的项目中，替换现有内容。
:::

<Steps>
1. 检查所有博客文章是否包含标签

    重新访问你的现有 Markdown 页面，确保每篇文章的前置数据中都包含一个 `tags` 数组。即使只有一个标签，它也应该被写成一个数组，例如 `tags: ["blogging"]`。

2. 创建包含所有现有标签的数组，使用 Astro 内置的 TypeScript 支持。

    添加以下代码，以提供一个包含博客文章中使用的每个标签的列表。

    ```astro title="src/pages/tags/[tag].astro" ins={7}
    ---
    import BaseLayout from '../../layouts/BaseLayout.astro';

    export async function getStaticPaths() {
      const allPosts = Object.values(import.meta.glob('../posts/*.md', { eager: true }));

      const uniqueTags = [...new Set(allPosts.map((post: any) => post.frontmatter.tags).flat())];
    ```

      <details>
      <summary>请详细说明这行代码正在做什么！</summary>

      毕竟这不是你自己编写的代码，看不懂是正常的！

      它逐个处理每个 Markdown 文章，并将每个标签数组组合成一个更大的数组。然后，它从找到的所有标签创建一个新的 Set（以忽略重复值）。最后，它将该 Set 转换为一个数组（不重复的），你可以用它来显示页面上的标签列表。
      </details>

    现在，你有一个名为 `uniqueTags` 的数组，其中包含元素项 `"astro"`、`"successes"`、`"community"`、`"blogging"`、`"setbacks"`、`"learning in public"`。

3. 替换 `getStaticPaths` 函数的 `return` 值

   ```js title="src/pages/tags/[tag].astro" del={1-8} ins={10-16}
   return [
     {params: {tag: "astro"}, props: {posts: allPosts}},
     {params: {tag: "successes"}, props: {posts: allPosts}},
     {params: {tag: "community"}, props: {posts: allPosts}},
     {params: {tag: "blogging"}, props: {posts: allPosts}},
     {params: {tag: "setbacks"}, props: {posts: allPosts}},
     {params: {tag: "learning in public"}, props: {posts: allPosts}}
   ]

   return uniqueTags.map((tag) => {
     const filteredPosts = allPosts.filter((post: any) => post.frontmatter.tags.includes(tag));
     return {
       params: { tag },
       props: { posts: filteredPosts },
     };
   });
   ```

    `getStaticPaths` 函数应始终返回一个包含 `params`（用于每个页面路由的名称）的对象列表，并且可以选择包含任何 `props`（要传递给这些页面的数据）。之前，你为你所知道的每个博客中使用的标签名称定义了一个参数，并将整个文章列表作为 props 传递给每个页面。

    现在，你可以使用 `uniqueTags` 数组自动生成此对象列表，以定义每个参数。

    现在，在将文章列表作为 props 发送到每个页面之前，应在此之前对其进行过滤。确保删除以前过滤文章的代码行，并更新 HTML 模板以使用 `posts` 而不是 `filteredPosts`。

    ``` astro title="src/pages/tags/[tag].astro" del={3,7} ins={8}
    const { tag } = Astro.params;
    const { posts } = Astro.props;
    const filteredPosts = posts.filter((post) => post.frontmatter.tags?.includes(tag));
    ---
    <!-- -->
    <ul>
        {filteredPosts.map((post: any) => <BlogPost url={post.url} title={post.frontmatter.title}/>)}
        {posts.map((post: any) => <BlogPost url={post.url} title={post.frontmatter.title}/>)}
    </ul>
    ```
</Steps>

### 最终代码示例

为了检查你的工作，或者如果你只想要完整、正确的代码以便复制到 `[tag].astro` 文件中，下面是你的 Astro 组件应该是这样的：

```astro title="src/pages/tags/[tag].astro"
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import BlogPost from '../../components/BlogPost.astro';

export async function getStaticPaths() {
  const allPosts = Object.values(import.meta.glob('../posts/*.md', { eager: true }));
  
  const uniqueTags = [...new Set(allPosts.map((post: any) => post.frontmatter.tags).flat())];

  return uniqueTags.map((tag) => {
    const filteredPosts = allPosts.filter((post: any) => post.frontmatter.tags.includes(tag));
    return {
      params: { tag },
      props: { posts: filteredPosts },
    };
  });
}

const { tag } = Astro.params;
const { posts } = Astro.props;
---
<BaseLayout pageTitle={tag}>
  <p>包含「{tag}」标签的文章</p>
  <ul>
    {posts.map((post: any) => <BlogPost url={post.url} title={post.frontmatter.title}/>)}
  </ul>
</BaseLayout>
```

现在，你应该能够在浏览器预览中访问任何一个标签页面。

在浏览器中导航到 `http://localhost:4321/tags/community`，你应该看到一个包含标签为 `community` 的博文列表。同样地，`http://localhost:4321/tags/learning%20in%20public` 应该显示一个包含标签为 `learning in public` 的博文列表。

在接下来的部分，你将创建导航链接到这些页面。

<Box icon="question-mark">

### 检验你的知识

选择与描述相匹配的术语。

1. 返回一个页面路由数组的函数。

    <MultipleChoice>
      <Option>params</Option>
      <Option>动态路由</Option>
      <Option isCorrect>`getStaticPaths()`</Option>
      <Option>props</Option>
    </MultipleChoice>

2. 从一个文件中创建多个页面路由的过程。

    <MultipleChoice>
      <Option>params</Option>
      <Option isCorrect>动态路由</Option>
      <Option>`getStaticPaths()`</Option>
      <Option>props</Option>
    </MultipleChoice>

4. 定义动态生成的页面路由名称的值。

    <MultipleChoice>
      <Option isCorrect>params</Option>
      <Option>动态路由</Option>
      <Option>`getStaticPaths()`</Option>
      <Option>props</Option>
    </MultipleChoice>

</Box>

<Box icon="check-list">

## 任务清单

<Checklist>
- [ ] 我可以动态生成页面。
- [ ] 我可以将 `props` 传递给每个页面路由。
</Checklist>
</Box>

### 相关资源

- [Astro 中的动态页面路由](/zh-cn/guides/routing/#动态路由)
- [`getStaticPaths()` API 文档](/zh-cn/reference/routing-reference/#getstaticpaths)
